一直以來,只要有有趣的東西推到我面前,我都願意去試一試。今年我參加 COSCUP 的時候,被身邊的講者跟朋友們深深感動。基於好奇,我參加了開源的社群,默默潛水觀察要從哪邊切入
感謝大佬推薦這個跟我名字只差一個字母的專案,讓我很好奇
這陣子我一邊觀摩大佬修 issue,一邊開始了解這個專案
說實話,我其實沒有很了解 Kafka,但我相信這是上天的旨意要我去認識它 XDDDD
以下內容整理自官方入門文件,並且用比喻的方式重新說明:
https://kafka.apache.org/43/getting-started/introduction/
想像一間公司有這些系統:
官網、App、訂單系統、庫存系統、會員系統 ……
現在業務提出一個很合理的需求:
「客人下單之後,庫存要扣、要發通知信、要記一筆帳」
最直覺的做法是:讓訂單系統直接去呼叫其他每一個系統
一開始還好,但系統一多就會變成這樣:
...
這種每個系統互相拉線的狀態,通常被稱為 義大利麵式整合
Kafka 要解決的就是這件事:
不要讓系統互相直接喊話,改成大家把「發生了什麼事」丟到一個中央的地方,有興趣的人自己來拿
事件(event) 就是一張記錄「某個時間點發生了什麼」的便條紙
它不是「現在的狀態」,而是「發生過的事實」
一個事件長這樣:
| 欄位 | 說明 | 範例 |
|---|---|---|
| Key(鍵) | 這件事跟誰/哪個東西有關 | user-8821 |
| Value(值) | 到底發生了什麼 | 付款 200 元給 Bob |
| Timestamp(時間戳記) | 什麼時候發生的 | 2020-06-25 14:06 |
| Headers(標頭) | 附註資訊,選填 | 來源: iOS App |
這裡有個很重要的思維轉換:
To
第二種寫法多了一個超能力:你可以回頭重播
餘額算錯了?重新跑一次
所謂的事件串流(event streaming),就是把這些便條紙即時收集起來、可靠地存好、可以即時或事後處理、並轉送到需要的地方
官方文件把它比喻為企業的「中樞神經系統」,意思是資訊在系統之間持續流動,而不是每次要用才去問
Kafka 像是一面超大的公用佈告欄
這跟傳統訊息佇列的典型用法最大的差別就在這裡:
| 傳統訊息佇列的典型用法 | Kafka | |
|---|---|---|
| 比喻 | 寄信 | 貼佈告欄 |
| 訊息被處理後 | 從佇列中移除 | 還在,別人也能讀 |
| 能重讀嗎 | 通常不行 | 可以,從任何位置重讀 |
| 資料留多久 | 留到被消費(或 TTL 到期) | 自己設定(7 天、30 天、永久都行) |
這張表是為了對比而簡化的,別當成絕對。像 RabbitMQ 也可以用 fanout exchange 讓多個佇列各拿一份訊息,近年更推出了 Streams 這種支援保留與重播的佇列型態。差別沒有表上那麼一刀兩斷,只是 Kafka 從第一天就以「留著」為預設。
「看完不刪」這件事聽起來很小,但它是 Kafka 整套設計的關鍵。因為資料還在,所以:
另外:
Kafka 的讀寫都是循序的,單筆讀寫的效能基本上不隨已存資料量增加而變慢,所以「資料留久一點」不會直接拖慢吞吐
但這不代表零成本:資料量大會拉長故障恢復和擴充時的搬遷時間,而且消費者一旦落後去讀冷資料,就會打到磁碟並影響同一台 broker 上的其他人
事件不是全部貼在同一面牆上,而是依類型分開
orders、payments、user-signups 各一面
官方文件的比喻是「主題像檔案夾,事件像檔案夾裡的檔案」
跟檔案夾不同的是,一個主題可以有任意多個人同時往裡面寫、任意多個人同時讀
任何往 Kafka 寫入事件的程式。例如訂單系統
任何從 Kafka 讀取並處理事件的程式。例如庫存系統、報表系統
這裡有個關鍵設計:生產者和消費者完全不認識彼此。
訂單系統只管貼上去,不需要知道有誰在讀、讀得多快、讀完做什麼
所以下游掛掉不會拖垮上游,新增下游也不用改上游的程式
這叫解耦(decoupling),是 Kafka 能擴充到很大規模的主因之一
一面佈告欄如果只能一個人貼,就會排隊排到天荒地老
所以 Kafka 把一個主題切成好幾條隊伍(分區),分散在不同機器上,大家可以同時貼、同時讀
隊伍內部是嚴格照順序的,貼上去只能接在最後面(append-only),不能插隊、不能修改
那怎麼決定一張便條要貼進哪條隊伍?看 Key
相同 Key 的事件會被分到同一條隊伍。舉個例子就懂了:
同一個 orders 主題裡,order-99001 的「已建立 -> 已付款 -> 已出貨」三個事件,因為 Key 都是 order-99001,會進同一條隊伍,所以讀的人保證會照這個順序讀到,不會出現「已出貨」跑在「已付款」前面的鬼故事
不過這裡有個前提,我一開始都不知道:
orders、payments、shipments 三個主題,跨主題就完全沒有順序保證了hash(key) % 分區數,分區數一調整,同一個 Key 的新事件就可能落到別條隊伍,跟舊事件的順序關係就斷了。這是實務上很有名的坑每條隊伍裡的便條紙都有編號,消費者自己記住「我讀到第 1523 號了」
因為書籤是消費者自己拿的,所以...
想重讀?把書籤往前搬
想從頭來?搬到 0
想只看新的?搬到最後面
實際負責存資料、處理讀寫請求的伺服器
一個 Kafka 叢集(cluster) 由一台到很多台 broker 組成,可以橫跨不同機房或雲端區域
小提醒:Kafka 4.x 已經完全移除 ZooKeeper,改用內建的 KRaft 來管理叢集中介資料。網路上很多文章還在教你先裝 ZooKeeper,看到的時候要注意版本
每個分區都可以複製到多台 broker 上,甚至跨機房、跨地理區域
其中一台掛掉,另一台立刻接手。這也讓你可以放心地一台一台重開機做維護
如果事件量太大,一個程式讀不完,可以開幾個實例組成一個群組
Kafka 會自動把分區分配給它們,一人負責幾條隊伍,不會重複讀
順帶一提:群組裡的實例數量超過分區數時,多出來的會閒置。所以分區數是你水平擴充的上限
而不同群組之間互不影響:
庫存系統群組和報表系統群組各自有自己的書籤,各讀各的。這正是「一份資料餵很多系統」的實作方式
把上面的名詞放進一個真實流程:
order-99001,Value 是訂單內容,寫進 orders 這個 Topic
acks=all 的設定下,等副本都寫好才回覆確認,訂單系統收到確認後回覆 App「下單成功」。整件事到這裡結束,訂單系統不需要等任何人官方把 Kafka 的能力歸納成:
對應到開發時會用到的 API:
| API | 白話用途 |
|---|---|
| Producer API | 把事件寫進去 |
| Consumer API | 把事件讀出來處理 |
| Kafka Streams API | 做即時運算:統計、彙總、join、時間視窗(如:「每 5 分鐘各門市的成交金額」) |
| Kafka Connect API | 大多數情況不用寫程式,用設定檔把資料庫、S3、Elasticsearch 等外部系統接上 Kafka |
| Admin API | 管理主題、broker 等 |
幾個特別值得注意:
官方列出的典型情境,共通點都是「資料一直在產生,而且要即時反應」:
如果上面有哪裡寫錯了,非常歡迎指正,我還在學